package scala.examples.tcpoly.dgp.gm;

trait GenericComp requires (CoreConvsComp with GenericComp) {
  trait Generic[g[x]] { // constructor class in Haskell
    def unit: g[Unit]
    def plus[a <% Rep[a], b <% Rep[b]]: g[Plus[a, b]]
    def pair[a <% Rep[a], b <% Rep[b]] : g[Pair[a, b]]
    def datatype[a <% Rep[a], b](iso: Iso[a,b]): g[b]
    def char: g[Char]
    def int: g[Int]
  
    implicit val self = this // for the implicit gen in rep
  }
  
  // higher-rank polymorphic function
  trait HOFunction1[from[_], to[_]] {
    def apply[x](a: from[x]): to[x]
  }

  trait Rep[t] {           
    def rep[m[x]](implicit gen: Generic[m]): m[t] // higher-order view bound really needs higher-rank polymorphism 
    // def rep[m[x] <% Generic[m[x]]]: m[t] 
    // --> def rep(implicit c0: Forall x. m[x] => Generic[m[x]]): m[t]
    // def rep[m[x]](implicit c0: HOFunction1[m, Generic[m]]): m[t] // use-case for type operators...  HOFunction1[[x] => m[x], [x] => Generic[m[x]]] 
  }
  
  trait Plus[a,b]
    case class Inl[a,b](el: a) extends Plus[a,b]
    case class Inr[a,b](el: b) extends Plus[a,b]

  case class Iso[t,dt](fromData: dt=>t, toData: t=>dt)
}

trait CoreConvsComp requires (CoreConvsComp with GenericComp) {
  type XConvs <: CoreConvs;  
  val convs: XConvs

  trait Convs {
    // TODO: requires higher-rank polymorphism 
//      def RepI[t]([g[x]] f: Generic[g]=>g[t]) = new Rep[t]{def rep[g[x]](implicit gen: Generic[g]): g[t]=f(gen)}    
  }

  trait CoreConvs requires XConvs extends Convs { 
    implicit def unit2rep(u: Unit): Rep[Unit] = new Rep[Unit]{def rep[g[x]](implicit gen: Generic[g]): g[Unit]= gen.unit }//  RepI (.unit)
    implicit def int2rep(i: Int): Rep[Int] = new Rep[Int]{def rep[g[x]](implicit gen: Generic[g]): g[Int]= gen.int } //RepI (.int)
    implicit def char2rep(c: Char): Rep[Char] = new Rep[Char]{def rep[g[x]](implicit gen: Generic[g]): g[Char]= gen.char } //RepI (.char)
    implicit def pair2rep[t, u](p: Pair[t, u])(implicit c1: t=>Rep[t], c2: u=>Rep[u]): Rep[Pair[t, u]] 
        = new Rep[Pair[t, u]]{def rep[g[x]](implicit gen: Generic[g]): g[Pair[t, u]]= gen.pair } //RepI (.pair(c1, c2)) // TODO: why do c1, c2 have to be passed explicitly? -- if I inline mr, it's not necessary
    implicit def plus2rep[t, u](p: Plus[t, u])(implicit c1: t=>Rep[t], c2: u=>Rep[u]): Rep[Plus[t, u]]  
        = new Rep[Plus[t, u]]{def rep[g[x]](implicit gen: Generic[g]): g[Plus[t, u]]= gen.plus } //RepI (.plus(c1, c2))
  }
}


trait ShowBinComp requires (CoreConvsComp with GenericComp with ShowBinComp) {
  case class ShowBin[a](apply: a => Bin)
  
  class ShowBinTC extends Generic[ShowBin] {
    def unit = ShowBin ((x: Unit) => Nil) 
    def plus[a <% Rep[a], b <% Rep[b]] = ShowBin[Plus[a, b]]({ 
      case Inl(el) => _0 :: this(el)
      case Inr(el) => _1 :: this(el) }) 
    
    def pair[a <% Rep[a], b <% Rep[b]] = ShowBin[Pair[a, b]](x =>
      this(x._1) ::: this(x._2))
    
    def datatype[a <% Rep[a], b](iso: Iso[a,b]) = ShowBin[b](x =>
      this(iso.fromData(x)))
    
    def char = ShowBin[Char](x => 
      bits(7,x))
      
    def int = ShowBin[Int](x =>
      bits(16,x))

    def apply[t <% Rep[t]](x: t) = x.rep[ShowBin](this).apply(x)
  }

  //implicit def showBinIfRep[t <% Rep[t]](x: t): Bin = new ShowBinTC.showBin(x)
  
  type Bin = List[Bit]
  trait Bit
    case object _0 extends Bit
    case object _1 extends Bit
  def bits(n: Int, x: Int): Bin = if(n==0) Nil else {if((x&1)==1) _1 else _0} :: bits(n-1,x>>1)
}


trait ListConvComp requires (CoreConvsComp with ListConvComp with GenericComp) {
  type XConvs <: (CoreConvs with ListConv)
  
  trait ListConv requires XConvs extends Convs {
    def fromList[t](l: List[t]): Plus[Unit, Pair[t, List[t]]] = l match {
      case Nil => Inl(())
      case x :: xs => Inr(Pair(x, xs))
    }
    
    def toList[t](l: Plus[Unit, Pair[t, List[t]]]): List[t] = l match {
      case Inl(())=> Nil 
      case Inr(Pair(x, xs)) => x :: xs 
    }
    
    implicit def list2rep[t <% Rep[t]](l: List[t]): Rep[List[t]] 
      = new Rep[List[t]]{def rep[g[x]](implicit gen: Generic[g]): g[List[t]]= gen.datatype(Iso(fromList[t], toList[t])) } //RepI (.datatype(Iso(fromList[t], toList[t])))  // TODO: have to supply [t], why?
  }     
}

object deploy extends GenericComp with ShowBinComp with CoreConvsComp with ListConvComp with Application {
  type XConvs = CoreConvs with ListConv
  val convs = new CoreConvs with ListConv
  val showBin = new ShowBinTC
  import convs._
  
  Console.println(showBin(3))
  Console.println(showBin(List(3,5)))
  Console.println(showBin("Lisa".toList))
}